{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# take a look at A Tool"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'name': 'wikipedia',\n",
       " 'description': 'A wrapper around Wikipedia. Useful for when you need to answer general questions about people, places, companies, facts, historical events, or other subjects. Input should be a search query.',\n",
       " 'input': {'query': {'title': 'Query', 'type': 'string'}},\n",
       " 'return_direct': False}"
      ]
     },
     "execution_count": 1,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from langchain_community.utilities import WikipediaAPIWrapper\n",
    "from langchain_community.tools import WikipediaQueryRun\n",
    "\n",
    "api_wrapper = WikipediaAPIWrapper(top_k_results=1, doc_content_chars_max=100)\n",
    "tool = WikipediaQueryRun(api_wrapper=api_wrapper)\n",
    "{'name': tool.name,\n",
    " 'description': tool.description,\n",
    " 'input': tool.args,\n",
    " 'return_direct': tool.return_direct,\n",
    " }"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'Page: LangChain\\nSummary: LangChain is a framework designed to simplify the creation of applications '"
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "tool.run({\"query\": \"langchain\"})"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# function call"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "from openai import OpenAI\n",
    "from datetime import datetime\n",
    "import json\n",
    "import os\n",
    "\n",
    "client = OpenAI(\n",
    "    api_key=os.getenv(\"DASHSCOPE_API_KEY\"), # 如果您没有配置环境变量，请在此处用您的API Key进行替换\n",
    "    base_url=\"https://dashscope.aliyuncs.com/compatible-mode/v1\",  # 填写DashScope SDK的base_url\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## define tools list"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 定义工具列表，模型在选择使用哪个工具时会参考工具的name和description\n",
    "tools = [\n",
    "    # 工具1 获取当前时刻的时间\n",
    "    {\n",
    "        \"type\": \"function\",\n",
    "        \"function\": {\n",
    "            \"name\": \"get_current_time\",\n",
    "            \"description\": \"当你想知道现在的时间时非常有用。\",\n",
    "            \"parameters\": {}  # 因为获取当前时间无需输入参数，因此parameters为空字典\n",
    "        }\n",
    "    },  \n",
    "    # 工具2 获取指定城市的天气\n",
    "    {\n",
    "        \"type\": \"function\",\n",
    "        \"function\": {\n",
    "            \"name\": \"get_current_weather\",\n",
    "            \"description\": \"当你想查询指定城市的天气时非常有用。\",\n",
    "            \"parameters\": {  # 查询天气时需要提供位置，因此参数设置为location\n",
    "                        \"type\": \"object\",\n",
    "                \"properties\": {\n",
    "                    \"location\": {\n",
    "                        \"type\": \"string\",\n",
    "                        \"description\": \"城市或县区，比如北京市、杭州市、余杭区等。\"\n",
    "                    }\n",
    "                }\n",
    "            },\n",
    "            \"required\": [\n",
    "                \"location\"\n",
    "            ]\n",
    "        }\n",
    "    }\n",
    "]\n",
    "\n",
    "# 模拟天气查询工具。返回结果示例：“北京今天是晴天。”\n",
    "def get_current_weather(location):\n",
    "    return f\"{location}今天是雨天。 \"\n",
    "\n",
    "# 查询当前时间的工具。返回结果示例：“当前时间：2024-04-15 17:15:18。“\n",
    "def get_current_time():\n",
    "    # 获取当前日期和时间\n",
    "    current_datetime = datetime.now()\n",
    "    # 格式化当前日期和时间\n",
    "    formatted_time = current_datetime.strftime('%Y-%m-%d %H:%M:%S')\n",
    "    # 返回格式化后的当前时间\n",
    "    return f\"当前时间：{formatted_time}。\""
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Invoke LLM with function calling"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "\n",
      "------------------------------------------------------------\n",
      "\n",
      "第1轮大模型输出信息：{'id': 'chatcmpl-aaf6e713-622a-9a7c-9024-b3d5056830f9', 'choices': [{'finish_reason': 'tool_calls', 'index': 0, 'logprobs': None, 'message': {'content': '', 'role': 'assistant', 'function_call': None, 'tool_calls': [{'id': 'call_88713c110c0b4442af9039', 'function': {'arguments': '{\"properties\": {\"location\": \"杭州市\"}}', 'name': 'get_current_weather'}, 'type': 'function', 'index': 0}]}}], 'created': 1721742531, 'model': 'qwen-plus', 'object': 'chat.completion', 'system_fingerprint': None, 'usage': {'completion_tokens': 21, 'prompt_tokens': 230, 'total_tokens': 251}}\n",
      "\n",
      "工具输出信息：杭州市今天是雨天。 \n",
      "\n",
      "------------------------------------------------------------\n",
      "第2轮大模型输出信息：{'content': '', 'role': 'assistant', 'function_call': None, 'tool_calls': [{'id': 'call_e302c8e9f6f64cdebe2720', 'function': {'arguments': '{\"properties\": {\"location\": \"北京市\"}}', 'name': 'get_current_weather'}, 'type': 'function', 'index': 0}]}\n",
      "\n",
      "工具输出信息：北京市今天是雨天。 \n",
      "\n",
      "------------------------------------------------------------\n",
      "第3轮大模型输出信息：{'content': '', 'role': 'assistant', 'function_call': None, 'tool_calls': [{'id': 'call_a91a2b8fe5624c728bca82', 'function': {'arguments': '{}', 'name': 'get_current_time'}, 'type': 'function', 'index': 0}]}\n",
      "\n",
      "工具输出信息：当前时间：2024-07-23 21:48:53。\n",
      "\n",
      "------------------------------------------------------------\n",
      "第4轮大模型输出信息：{'content': '目前，杭州市和北京市的天气都是雨天。现在的具体时间是2024年07月23日21点48分53秒。请记得带伞出行！', 'role': 'assistant', 'function_call': None, 'tool_calls': None}\n",
      "\n",
      "最终答案：目前，杭州市和北京市的天气都是雨天。现在的具体时间是2024年07月23日21点48分53秒。请记得带伞出行！\n"
     ]
    }
   ],
   "source": [
    "# 封装模型响应函数\n",
    "def get_response(messages):\n",
    "    completion = client.chat.completions.create(\n",
    "        model=\"qwen-plus\",\n",
    "        messages=messages,\n",
    "        tools=tools\n",
    "        )\n",
    "    return completion.model_dump()\n",
    "\n",
    "def call_with_messages():\n",
    "    print('\\n')\n",
    "    messages = [\n",
    "            {\n",
    "                \"content\": \"杭州和北京天气怎么样？现在几点了？\",\n",
    "                \"role\": \"user\"\n",
    "            }\n",
    "    ]\n",
    "    print(\"-\"*60)\n",
    "    # 模型的第一轮调用\n",
    "    i = 1\n",
    "    first_response = get_response(messages)\n",
    "    assistant_output = first_response['choices'][0]['message']\n",
    "    print(f\"\\n第{i}轮大模型输出信息：{first_response}\\n\")\n",
    "    if  assistant_output['content'] is None:\n",
    "        assistant_output['content'] = \"\"\n",
    "    messages.append(assistant_output)\n",
    "    # 如果不需要调用工具，则直接返回最终答案\n",
    "    if assistant_output['tool_calls'] == None:  # 如果模型判断无需调用工具，则将assistant的回复直接打印出来，无需进行模型的第二轮调用\n",
    "        print(f\"无需调用工具，我可以直接回复：{assistant_output['content']}\")\n",
    "        return\n",
    "    # 如果需要调用工具，则进行模型的多轮调用，直到模型判断无需调用工具\n",
    "    while assistant_output['tool_calls'] != None:\n",
    "        # 如果判断需要调用查询天气工具，则运行查询天气工具\n",
    "        if assistant_output['tool_calls'][0]['function']['name'] == 'get_current_weather':\n",
    "            tool_info = {\"name\": \"get_current_weather\", \"role\":\"tool\"}\n",
    "            # 提取位置参数信息\n",
    "            arguments = assistant_output['tool_calls'][0]['function']['arguments']\n",
    "            if 'properties' in arguments:\n",
    "                location = arguments['properties']['location']\n",
    "            else:\n",
    "                location = arguments['location']\n",
    "            tool_info['content'] = get_current_weather(location)\n",
    "        # 如果判断需要调用查询时间工具，则运行查询时间工具\n",
    "        elif assistant_output['tool_calls'][0]['function']['name'] == 'get_current_time':\n",
    "            tool_info = {\"name\": \"get_current_time\", \"role\":\"tool\"}\n",
    "            tool_info['content'] = get_current_time()\n",
    "        print(f\"工具输出信息：{tool_info['content']}\\n\")\n",
    "        print(\"-\"*60)\n",
    "        messages.append(tool_info)\n",
    "        assistant_output = get_response(messages)['choices'][0]['message']\n",
    "        if  assistant_output['content'] is None:\n",
    "            assistant_output['content'] = \"\"\n",
    "        messages.append(assistant_output)\n",
    "        i += 1\n",
    "        print(f\"第{i}轮大模型输出信息：{assistant_output}\\n\")\n",
    "    print(f\"最终答案：{assistant_output['content']}\")\n",
    "\n",
    "\n",
    "call_with_messages()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# LangChain Tools as OpenAI Functions\n",
    "## Defining Custom Tools\n",
    "### @tool decorator"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Import things that are needed generically\n",
    "from langchain.pydantic_v1 import BaseModel, Field\n",
    "from langchain.tools import BaseTool, StructuredTool, tool\n",
    "\n",
    "@tool\n",
    "def get_current_weather(location: str) -> str:\n",
    "    \"\"\"当你想查询指定城市的天气时非常有用。\"\"\"\n",
    "    return f\"{location}今天是雨天。\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "('get_current_weather',\n",
       " 'get_current_weather(location: str) -> str - 当你想查询指定城市的天气时非常有用。',\n",
       " {'location': {'title': 'Location', 'type': 'string'}})"
      ]
     },
     "execution_count": 20,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "get_current_weather.name, get_current_weather.description, get_current_weather.args"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### tool decorator with JSON args"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [],
   "source": [
    "class InputSchema(BaseModel):\n",
    "    location: str = Field(description=\"城市或县区，比如北京市、杭州市、余杭区等。\")\n",
    "\n",
    "@tool(\"get_current_weather\", args_schema=InputSchema)\n",
    "def get_current_weather(location: str):\n",
    "    \"\"\"当你想查询指定城市的天气时非常有用。\"\"\"\n",
    "    return f\"{location}今天是雨天。\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "('get_current_weather',\n",
       " 'get_current_weather(location: str) - 当你想查询指定城市的天气时非常有用。',\n",
       " {'location': {'title': 'Location',\n",
       "   'description': '城市或县区，比如北京市、杭州市、余杭区等。',\n",
       "   'type': 'string'}})"
      ]
     },
     "execution_count": 22,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "get_current_weather.name, get_current_weather.description, get_current_weather.args"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Subclass BaseTool"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {},
   "outputs": [],
   "source": [
    "from typing import Optional, Type\n",
    "\n",
    "from langchain.callbacks.manager import (\n",
    "    AsyncCallbackManagerForToolRun,\n",
    "    CallbackManagerForToolRun,\n",
    ")\n",
    "\n",
    "class InputSchema(BaseModel):\n",
    "    location: str = Field(description=\"城市或县区，比如北京市、杭州市、余杭区等。\")\n",
    "\n",
    "class GetCurrentWeatherTool(BaseTool):\n",
    "    name = \"get_current_weather\"\n",
    "    description = \"当你想查询指定城市的天气时非常有用。\"\n",
    "    args_schema: Type[BaseModel] = InputSchema\n",
    "\n",
    "    def _run(\n",
    "        self, location: str, run_manager: Optional[CallbackManagerForToolRun] = None\n",
    "    ) -> str:\n",
    "        \"\"\"Use the tool.\"\"\"\n",
    "        return f\"{location}今天是雨天。\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "('get_current_weather',\n",
       " '当你想查询指定城市的天气时非常有用。',\n",
       " {'location': {'title': 'Location',\n",
       "   'description': '城市或县区，比如北京市、杭州市、余杭区等。',\n",
       "   'type': 'string'}})"
      ]
     },
     "execution_count": 25,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "get_current_weather = GetCurrentWeatherTool()\n",
    "get_current_weather.name, get_current_weather.description, get_current_weather.args"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### StructuredTool dataclass"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {},
   "outputs": [],
   "source": [
    "class InputSchema(BaseModel):\n",
    "    location: str = Field(description=\"城市或县区，比如北京市、杭州市、余杭区等。\")\n",
    "\n",
    "def get_current_weather_func(location: str):\n",
    "    \"\"\"当你想查询指定城市的天气时非常有用。\"\"\"\n",
    "    return f\"{location}今天是雨天。\"\n",
    "\n",
    "get_current_weather = StructuredTool.from_function(\n",
    "    func=get_current_weather_func,\n",
    "    name=\"get_current_weather\",\n",
    "    description=\"当你想查询指定城市的天气时非常有用。\",\n",
    "    args_schema=InputSchema\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "('get_current_weather',\n",
       " 'get_current_weather(location: str) - 当你想查询指定城市的天气时非常有用。',\n",
       " {'location': {'title': 'Location',\n",
       "   'description': '城市或县区，比如北京市、杭州市、余杭区等。',\n",
       "   'type': 'string'}})"
      ]
     },
     "execution_count": 27,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "get_current_weather.name, get_current_weather.description, get_current_weather.args"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Tools as OpenAI Functions "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_core.messages import HumanMessage, AIMessage, ToolMessage\n",
    "from tools.function_calling import convert_to_openai_tool"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "class WeatherSchema(BaseModel):\n",
    "    location: str = Field(description=\"城市或县区，比如北京市、杭州市、余杭区等。\")\n",
    "\n",
    "@tool(\"get_current_weather\", args_schema=WeatherSchema)\n",
    "def get_current_weather(location: str):\n",
    "    \"\"\"当你想查询指定城市的天气时非常有用。\"\"\"\n",
    "    return f\"{location}今天是雨天。\"\n",
    "\n",
    "\n",
    "# 查询当前时间的工具。返回结果示例：“当前时间：2024-04-15 17:15:18。“\n",
    "@tool(\"get_current_time\")\n",
    "def get_current_time():\n",
    "    \"\"\"当你想知道现在的时间时非常有用。\"\"\"\n",
    "    # 获取当前日期和时间\n",
    "    current_datetime = datetime.now()\n",
    "    # 格式化当前日期和时间\n",
    "    formatted_time = current_datetime.strftime('%Y-%m-%d %H:%M:%S')\n",
    "    # 返回格式化后的当前时间\n",
    "    return f\"当前时间：{formatted_time}。\"\n",
    "\n",
    "functions = [get_current_weather, get_current_time]\n",
    "tools = [convert_to_openai_tool(t) for t in functions]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[{'type': 'function',\n",
       "  'function': {'name': 'get_current_weather',\n",
       "   'description': 'get_current_weather(location: str) - 当你想查询指定城市的天气时非常有用。',\n",
       "   'parameters': {'type': 'object',\n",
       "    'properties': {'location': {'description': '城市或县区，比如北京市、杭州市、余杭区等。',\n",
       "      'type': 'string'}},\n",
       "    'required': ['location']}}},\n",
       " {'type': 'function',\n",
       "  'function': {'name': 'get_current_time',\n",
       "   'description': 'get_current_time() - 当你想知道现在的时间时非常有用。',\n",
       "   'parameters': {'type': 'object', 'properties': {}}}}]"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "tools"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "AIMessage(content='', additional_kwargs={'tool_calls': [{'function': {'name': 'get_current_weather', 'arguments': '{\"location\": \"杭州市\"}'}, 'index': 0, 'id': 'call_c72afbea224b4de6bc3957', 'type': 'function'}]}, response_metadata={'model_name': 'qwen-plus', 'finish_reason': 'tool_calls', 'request_id': '6ee616af-aa17-9600-bd04-8bcaf00e2f56', 'token_usage': {'input_tokens': 261, 'output_tokens': 18, 'total_tokens': 279}}, id='run-63bc17f2-8f36-41c2-b890-96d3a7ab7376-0', tool_calls=[{'name': 'get_current_weather', 'args': {'location': '杭州市'}, 'id': 'call_c72afbea224b4de6bc3957'}])"
      ]
     },
     "execution_count": 13,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from tongyi.chat_model import CustomChatTongyi\n",
    "\n",
    "model = CustomChatTongyi(model='qwen-plus')\n",
    "\n",
    "messages = [HumanMessage(content=\"杭州和北京天气怎么样？现在几点了？\")]\n",
    "\n",
    "assistant_output = model.invoke(messages, tools=tools)\n",
    "assistant_output"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "工具输出信息：杭州市今天是雨天。\n",
      "\n",
      "------------------------------------------------------------\n",
      "大模型输出信息：content='' additional_kwargs={'tool_calls': [{'function': {'name': 'get_current_weather', 'arguments': '{\"location\": \"北京市\"}'}, 'index': 0, 'id': 'call_6dfa56e25ab84322b5cc2f', 'type': 'function'}]} response_metadata={'model_name': 'qwen-plus', 'finish_reason': 'tool_calls', 'request_id': '88873f57-7d98-962f-af86-7e4b3e2c9a80', 'token_usage': {'input_tokens': 294, 'output_tokens': 20, 'total_tokens': 314}} id='run-6d079aaa-9469-4be9-9c22-7a0f97e9dd85-0' tool_calls=[{'name': 'get_current_weather', 'args': {'location': '北京市'}, 'id': 'call_6dfa56e25ab84322b5cc2f'}]\n",
      "\n",
      "工具输出信息：北京市今天是雨天。\n",
      "\n",
      "------------------------------------------------------------\n",
      "大模型输出信息：content='' additional_kwargs={'tool_calls': [{'function': {'name': 'get_current_time', 'arguments': '{\"properties\": {}}'}, 'index': 0, 'id': 'call_97f6441d9b9d4503ae2dea', 'type': 'function'}]} response_metadata={'model_name': 'qwen-plus', 'finish_reason': 'tool_calls', 'request_id': '62145e44-b81a-9bf9-aa5f-26f76f598fd3', 'token_usage': {'input_tokens': 330, 'output_tokens': 19, 'total_tokens': 349}} id='run-e8cbd101-d23e-48e5-8b20-24a4e88589d2-0' tool_calls=[{'name': 'get_current_time', 'args': {'properties': {}}, 'id': 'call_97f6441d9b9d4503ae2dea'}]\n",
      "\n",
      "工具输出信息：当前时间：2024-07-26 12:16:34。\n",
      "\n",
      "------------------------------------------------------------\n",
      "大模型输出信息：content='目前杭州市和北京市的天气都是雨天。现在的时间是2024年7月26日12点16分34秒。记得带伞出门哦！' response_metadata={'model_name': 'qwen-plus', 'finish_reason': 'stop', 'request_id': 'e7c81621-8e39-981a-8a9f-de3f2a172106', 'token_usage': {'input_tokens': 380, 'output_tokens': 42, 'total_tokens': 422}} id='run-d5d14038-4454-4ad5-9375-c4f1384e0b70-0'\n",
      "\n"
     ]
    }
   ],
   "source": [
    "functions_to_call = {'get_current_weather': get_current_weather, 'get_current_time': get_current_time}\n",
    "\n",
    "while assistant_output.tool_calls:\n",
    "    messages.append(assistant_output)\n",
    "    for tool in assistant_output.tool_calls:\n",
    "        args = tool['args'] if 'properties' not in tool['args'] else tool['args']['properties']\n",
    "        tool_content = functions_to_call[tool['name']].invoke(args)\n",
    "        messages.append(ToolMessage(name=tool['name'], \n",
    "                                    tool_call_id=tool['id'], \n",
    "                                    content=tool_content)\n",
    "                        )\n",
    "        print(f\"工具输出信息：{tool_content}\\n\")\n",
    "        print(\"-\"*60)\n",
    "    \n",
    "    assistant_output = model.invoke(messages, tools=tools)\n",
    "    print(f\"大模型输出信息：{assistant_output}\\n\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### bind_tools"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "AIMessage(content='', additional_kwargs={'tool_calls': [{'function': {'name': 'get_current_weather', 'arguments': '{\"location\": \"杭州市\"}'}, 'index': 0, 'id': 'call_72d4e08d6ddc4fe5974f72', 'type': 'function'}]}, response_metadata={'model_name': 'qwen-plus', 'finish_reason': 'tool_calls', 'request_id': '86640a08-c7fd-9c0a-a350-0deb481bf5d1', 'token_usage': {'input_tokens': 261, 'output_tokens': 18, 'total_tokens': 279}}, id='run-c663249c-2967-41e6-a695-88269dcc4446-0', tool_calls=[{'name': 'get_current_weather', 'args': {'location': '杭州市'}, 'id': 'call_72d4e08d6ddc4fe5974f72'}])"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "model_with_tools = model.bind_tools(functions)\n",
    "\n",
    "messages = [HumanMessage(content=\"杭州和北京天气怎么样？现在几点了？\")]\n",
    "\n",
    "assistant_output = model_with_tools.invoke(messages)\n",
    "assistant_output"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Agents\n",
    "## Quickstart"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain.prompts import (\n",
    "    ChatPromptTemplate,\n",
    "    PromptTemplate,\n",
    "    HumanMessagePromptTemplate,\n",
    "    MessagesPlaceholder,\n",
    ")\n",
    "\n",
    "prompt = ChatPromptTemplate.from_messages(\n",
    "    [MessagesPlaceholder(variable_name='chat_history', optional=True),\n",
    "     HumanMessagePromptTemplate(prompt=PromptTemplate(input_variables=['input'], template='{input}')),\n",
    "     MessagesPlaceholder(variable_name='agent_scratchpad')]\n",
    "    )"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "from datetime import datetime\n",
    "from langchain.pydantic_v1 import BaseModel, Field\n",
    "from langchain.tools import tool\n",
    "\n",
    "class WeatherSchema(BaseModel):\n",
    "    location: str = Field(description=\"城市或县区，比如北京市、杭州市、余杭区等。\")\n",
    "\n",
    "@tool(\"get_current_weather\", args_schema=WeatherSchema)\n",
    "def get_current_weather(location: str):\n",
    "    \"\"\"当你想查询指定城市的天气时非常有用。\"\"\"\n",
    "    return f\"{location}今天是雨天。\"\n",
    "\n",
    "\n",
    "# 查询当前时间的工具。返回结果示例：“当前时间：2024-04-15 17:15:18。“\n",
    "@tool(\"get_current_time\")\n",
    "def get_current_time():\n",
    "    \"\"\"当你想知道现在的时间时非常有用。\"\"\"\n",
    "    # 获取当前日期和时间\n",
    "    current_datetime = datetime.now()\n",
    "    # 格式化当前日期和时间\n",
    "    formatted_time = current_datetime.strftime('%Y-%m-%d %H:%M:%S')\n",
    "    # 返回格式化后的当前时间\n",
    "    return f\"当前时间：{formatted_time}。\"\n",
    "\n",
    "tools = [get_current_weather, get_current_time]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain.agents import create_tool_calling_agent\n",
    "from langchain.agents import AgentExecutor\n",
    "\n",
    "from tongyi.chat_model import CustomChatTongyi\n",
    "\n",
    "model = CustomChatTongyi(model='qwen-plus')\n",
    "\n",
    "agent = create_tool_calling_agent(model, tools, prompt)\n",
    "agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "\n",
      "\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n",
      "\u001b[32;1m\u001b[1;3m\n",
      "Invoking: `get_current_weather` with `{'location': '杭州市'}`\n",
      "\n",
      "\n",
      "\u001b[0m\u001b[36;1m\u001b[1;3m杭州市今天是雨天。\u001b[0m\u001b[32;1m\u001b[1;3m\n",
      "Invoking: `get_current_weather` with `{'location': '北京市'}`\n",
      "\n",
      "\n",
      "\u001b[0m\u001b[36;1m\u001b[1;3m北京市今天是雨天。\u001b[0m\u001b[32;1m\u001b[1;3m\n",
      "Invoking: `get_current_time` with `{}`\n",
      "\n",
      "\n",
      "\u001b[0m\u001b[33;1m\u001b[1;3m当前时间：2024-07-30 15:35:17。\u001b[0m\u001b[32;1m\u001b[1;3m目前杭州市和北京市的天气都是雨天。现在的时间是2024年7月30日15点35分17秒。记得带伞出门哦！\u001b[0m\n",
      "\n",
      "\u001b[1m> Finished chain.\u001b[0m\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "{'input': '杭州和北京天气怎么样？现在几点了？',\n",
       " 'output': '目前杭州市和北京市的天气都是雨天。现在的时间是2024年7月30日15点35分17秒。记得带伞出门哦！'}"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "agent_executor.invoke({'input': '杭州和北京天气怎么样？现在几点了？'})"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## XML Agent"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[HumanMessagePromptTemplate(prompt=PromptTemplate(input_variables=['agent_scratchpad', 'chat_history', 'input', 'tools'], template=\"You are a helpful assistant. Help the user answer any questions.\\n\\nYou have access to the following tools:\\n\\n{tools}\\n\\nIn order to use a tool, you can use <tool></tool> and <tool_input></tool_input> tags. You will then get back a response in the form <observation></observation>\\nFor example, if you have a tool called 'search' that could run a google search, in order to search for the weather in SF you would respond:\\n\\n<tool>search</tool><tool_input>weather in SF</tool_input>\\n<observation>64 degrees</observation>\\n\\nWhen you are done, respond with a final answer between <final_answer></final_answer>. For example:\\n\\n<final_answer>The weather in SF is 64 degrees</final_answer>\\n\\nBegin!\\n\\nPrevious Conversation:\\n{chat_history}\\n\\nQuestion: {input}\\n{agent_scratchpad}\"))]"
      ]
     },
     "execution_count": 1,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from langchain import hub\n",
    "prompt = hub.pull(\"hwchase17/xml-agent-convo\")\n",
    "prompt.messages"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "from datetime import datetime\n",
    "from langchain.pydantic_v1 import BaseModel, Field\n",
    "\n",
    "from tools.tools import ModifyStructuredTool\n",
    "\n",
    "class WeatherSchema(BaseModel):\n",
    "    location: str = Field(description=\"城市或县区，比如北京市、杭州市、余杭区等。\")\n",
    "\n",
    "def get_current_weather_func(location: str):\n",
    "    \"\"\"当你想查询指定城市的天气时非常有用。\"\"\"\n",
    "    return f\"{location}今天是雨天。\"\n",
    "\n",
    "\n",
    "get_current_weather = ModifyStructuredTool.from_function(\n",
    "    func=get_current_weather_func,\n",
    "    name=\"get_current_weather\",\n",
    "    description=\"当你想查询指定城市的天气时非常有用。\",\n",
    "    args_schema=WeatherSchema\n",
    ")\n",
    "\n",
    "\n",
    "def get_current_time_func():\n",
    "    \"\"\"当你想知道现在的时间时非常有用。\"\"\"\n",
    "    # 获取当前日期和时间\n",
    "    current_datetime = datetime.now()\n",
    "    # 格式化当前日期和时间\n",
    "    formatted_time = current_datetime.strftime('%Y-%m-%d %H:%M:%S')\n",
    "    # 返回格式化后的当前时间\n",
    "    return f\"当前时间：{formatted_time}。\"\n",
    "\n",
    "\n",
    "get_current_time = ModifyStructuredTool.from_function(\n",
    "    func=get_current_time_func,\n",
    "    name=\"get_current_time\",\n",
    "    description=\"当你想知道现在的时间时非常有用。\",\n",
    ")\n",
    "\n",
    "tools = [get_current_weather, get_current_time]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain.agents import create_xml_agent\n",
    "from langchain.agents import AgentExecutor\n",
    "\n",
    "from tongyi.chat_model import CustomChatTongyi\n",
    "\n",
    "model = CustomChatTongyi(model='qwen-plus')\n",
    "\n",
    "agent = create_xml_agent(model, tools, prompt)\n",
    "agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True, return_intermediate_steps=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "\n",
      "\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n",
      "\u001b[32;1m\u001b[1;3m<tool>get_current_weather</tool><tool_input>杭州\u001b[0m\u001b[36;1m\u001b[1;3m杭州今天是雨天。\u001b[0m\u001b[32;1m\u001b[1;3m<tool>get_current_weather</tool><tool_input>北京\u001b[0m\u001b[36;1m\u001b[1;3m北京今天是雨天。\u001b[0m\u001b[32;1m\u001b[1;3m<tool>get_current_time</tool><tool_input>\u001b[0m\u001b[33;1m\u001b[1;3m当前时间：2024-07-31 16:59:52。\u001b[0m\u001b[32;1m\u001b[1;3m<final_answer>杭州和北京今天的天气都是雨天。现在的时间是2024年7月31日16点59分52秒。</final_answer>\u001b[0m\n",
      "\n",
      "\u001b[1m> Finished chain.\u001b[0m\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "{'input': '杭州和北京天气怎么样？现在几点了？',\n",
       " 'output': '杭州和北京今天的天气都是雨天。现在的时间是2024年7月31日16点59分52秒。',\n",
       " 'intermediate_steps': [(AgentAction(tool='get_current_weather', tool_input='杭州', log='<tool>get_current_weather</tool><tool_input>杭州'),\n",
       "   '杭州今天是雨天。'),\n",
       "  (AgentAction(tool='get_current_weather', tool_input='北京', log='<tool>get_current_weather</tool><tool_input>北京'),\n",
       "   '北京今天是雨天。'),\n",
       "  (AgentAction(tool='get_current_time', tool_input='', log='<tool>get_current_time</tool><tool_input>'),\n",
       "   '当前时间：2024-07-31 16:59:52。')]}"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "agent_executor.invoke({'input': '杭州和北京天气怎么样？现在几点了？'})"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Agent Iterator"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Agent的第 1 步输出:\n",
      "\taction: tool='get_current_weather' tool_input={'location': '杭州市'} log=\"\\nInvoking: `get_current_weather` with `{'location': '杭州市'}`\\n\\n\\n\" message_log=[AIMessageChunk(content='', additional_kwargs={'tool_calls': [{'index': 0, 'id': 'call_c41f51d0064b46e1af76e3call_c41f51d0064b46e1af76e3call_c41f51d0064b46e1af76e3call_c41f51d0064b46e1af76e3', 'type': 'functionfunctionfunctionfunction', 'function': {'name': 'get_current_weather', 'arguments': '{\"location\": \"杭州市\"}'}}]}, response_metadata={'finish_reason': 'tool_calls', 'request_id': '43be987d-0fc2-9a71-8f34-178dbacc5db6', 'token_usage': {'input_tokens': 261, 'output_tokens': 18, 'total_tokens': 279}, 'model_name': 'qwen-plus'}, id='run-dd8f971c-95ae-47fb-b820-3cb3d3ed158f', tool_calls=[{'name': 'get_current_weather', 'args': {'location': '杭州市'}, 'id': 'call_c41f51d0064b46e1af76e3call_c41f51d0064b46e1af76e3call_c41f51d0064b46e1af76e3call_c41f51d0064b46e1af76e3'}], tool_call_chunks=[{'name': 'get_current_weather', 'args': '{\"location\": \"杭州市\"}', 'id': 'call_c41f51d0064b46e1af76e3call_c41f51d0064b46e1af76e3call_c41f51d0064b46e1af76e3call_c41f51d0064b46e1af76e3', 'index': 0}])] tool_call_id='call_c41f51d0064b46e1af76e3call_c41f51d0064b46e1af76e3call_c41f51d0064b46e1af76e3call_c41f51d0064b46e1af76e3'\n",
      "\tvalue: 杭州市今天是雨天。\n",
      "Agent的第 2 步输出:\n",
      "\taction: tool='get_current_weather' tool_input={'location': '北京市'} log=\"\\nInvoking: `get_current_weather` with `{'location': '北京市'}`\\n\\n\\n\" message_log=[AIMessageChunk(content='', additional_kwargs={'tool_calls': [{'index': 0, 'id': 'call_5811111a156544cb9359afcall_5811111a156544cb9359afcall_5811111a156544cb9359afcall_5811111a156544cb9359af', 'type': 'functionfunctionfunctionfunction', 'function': {'name': 'get_current_weather', 'arguments': '{\"location\": \"北京市\"}'}}]}, response_metadata={'finish_reason': 'tool_calls', 'request_id': '7e3e3648-94a3-9c08-80c2-b7fbba5dfa1c', 'token_usage': {'input_tokens': 294, 'output_tokens': 20, 'total_tokens': 314}, 'model_name': 'qwen-plus'}, id='run-8cef4d44-5b0b-429c-9674-848b9e0c5754', tool_calls=[{'name': 'get_current_weather', 'args': {'location': '北京市'}, 'id': 'call_5811111a156544cb9359afcall_5811111a156544cb9359afcall_5811111a156544cb9359afcall_5811111a156544cb9359af'}], tool_call_chunks=[{'name': 'get_current_weather', 'args': '{\"location\": \"北京市\"}', 'id': 'call_5811111a156544cb9359afcall_5811111a156544cb9359afcall_5811111a156544cb9359afcall_5811111a156544cb9359af', 'index': 0}])] tool_call_id='call_5811111a156544cb9359afcall_5811111a156544cb9359afcall_5811111a156544cb9359afcall_5811111a156544cb9359af'\n",
      "\tvalue: 北京市今天是雨天。\n",
      "Agent的第 3 步输出:\n",
      "\taction: tool='get_current_time' tool_input={'properties': {}} log=\"\\nInvoking: `get_current_time` with `{'properties': {}}`\\n\\n\\n\" message_log=[AIMessageChunk(content='', additional_kwargs={'tool_calls': [{'index': 0, 'id': 'call_6ea8708dd1e744e1990b27call_6ea8708dd1e744e1990b27call_6ea8708dd1e744e1990b27call_6ea8708dd1e744e1990b27', 'type': 'functionfunctionfunctionfunction', 'function': {'name': 'get_current_time', 'arguments': '{\"properties\": {}}'}}]}, response_metadata={'finish_reason': 'tool_calls', 'request_id': '3bebe653-ad7f-93a8-9507-72df8d63a4b6', 'token_usage': {'input_tokens': 330, 'output_tokens': 19, 'total_tokens': 349}, 'model_name': 'qwen-plus'}, id='run-ce68be67-f453-4ccc-83cd-13648e54ea74', tool_calls=[{'name': 'get_current_time', 'args': {'properties': {}}, 'id': 'call_6ea8708dd1e744e1990b27call_6ea8708dd1e744e1990b27call_6ea8708dd1e744e1990b27call_6ea8708dd1e744e1990b27'}], tool_call_chunks=[{'name': 'get_current_time', 'args': '{\"properties\": {}}', 'id': 'call_6ea8708dd1e744e1990b27call_6ea8708dd1e744e1990b27call_6ea8708dd1e744e1990b27call_6ea8708dd1e744e1990b27', 'index': 0}])] tool_call_id='call_6ea8708dd1e744e1990b27call_6ea8708dd1e744e1990b27call_6ea8708dd1e744e1990b27call_6ea8708dd1e744e1990b27'\n",
      "\tvalue: 当前时间：2024-08-01 08:04:33。\n",
      "Agent的第 4 步输出:\n",
      "目前杭州市和北京市的天气都是雨天。现在的时间是2024年8月1日8点04分33秒。请记得带伞出门哦！\n"
     ]
    }
   ],
   "source": [
    "agent_executor = AgentExecutor(agent=agent, tools=tools)\n",
    "\n",
    "for i, step in enumerate(agent_executor.iter({'input': '杭州和北京天气怎么样？现在几点了？'})):\n",
    "    print(f'Agent的第 {i+1} 步输出:')\n",
    "    if output := step.get(\"intermediate_step\"):\n",
    "        action, value = output[0]\n",
    "        print(f'\\taction: {action}')\n",
    "        print(f'\\tvalue: {value}')\n",
    "    else:\n",
    "        print(step['output'])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Structured Output"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "from typing import List, Dict\n",
    "\n",
    "from langchain_core.agents import AgentActionMessageLog, AgentFinish, AgentAction\n",
    "from langchain.agents.output_parsers.tools import parse_ai_message_to_tool_action\n",
    "from langchain_core.pydantic_v1 import BaseModel, Field\n",
    "\n",
    "\n",
    "class Response(BaseModel):\n",
    "    \"\"\"最终的回答如果包含天气、时间等信息，则需要调用该工具\"\"\"\n",
    "\n",
    "    weather: Dict[str, str] = Field(\n",
    "        description='最终的回答中不同城市对应的天气，比如北京的天气为阴天，则返回`{\"北京\": \"阴天\"}`。如果不存在天气信息则忽略', default={})\n",
    "    current_time: str = Field(\n",
    "        description=\"最终的回答中的当前时间，如果不存在则忽略\", default=\"\")\n",
    "\n",
    "\n",
    "def parse(output):\n",
    "    name = None\n",
    "    # 判断调用的工具是否为 Response\n",
    "    if output.tool_calls:\n",
    "        tool_calls = output.tool_calls[0]\n",
    "        name = tool_calls[\"name\"]\n",
    "        inputs = tool_calls[\"args\"]\n",
    "    elif output.additional_kwargs.get(\"tool_calls\"):\n",
    "        tool_calls = output.additional_kwargs[\"tool_calls\"][0]\n",
    "        name = tool_calls[\"name\"]\n",
    "        inputs = tool_calls[\"args\"]\n",
    "    \n",
    "    if name == \"Response\":\n",
    "        return AgentFinish(return_values=inputs, log=str(tool_calls))\n",
    "\n",
    "    return parse_ai_message_to_tool_action(output)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "# prompt & tools的定义在上面[Agents-Quickstart]小节,不再重复编写\n",
    "from langchain.agents.format_scratchpad import format_to_tool_messages\n",
    "from langchain.agents import AgentExecutor\n",
    "from tongyi.chat_model import CustomChatTongyi\n",
    "\n",
    "model = CustomChatTongyi(model='qwen-plus')\n",
    "llm_with_tools = model.bind_tools(tools + [Response])\n",
    "\n",
    "agent = (\n",
    "    {\n",
    "        \"input\": lambda x: x[\"input\"],\n",
    "        # Format agent scratchpad from intermediate steps\n",
    "        \"agent_scratchpad\": lambda x: format_to_tool_messages(\n",
    "            x[\"intermediate_steps\"]\n",
    "        ),\n",
    "    }\n",
    "    | prompt\n",
    "    | llm_with_tools\n",
    "    | parse\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "\n",
      "\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n",
      "\u001b[32;1m\u001b[1;3m\n",
      "Invoking: `get_current_weather` with `{'location': '杭州市'}`\n",
      "\n",
      "\n",
      "\u001b[0m\u001b[36;1m\u001b[1;3m杭州市今天是雨天。\u001b[0m\u001b[32;1m\u001b[1;3m\n",
      "Invoking: `get_current_weather` with `{'location': '北京市'}`\n",
      "\n",
      "\n",
      "\u001b[0m\u001b[36;1m\u001b[1;3m北京市今天是雨天。\u001b[0m\u001b[32;1m\u001b[1;3m\n",
      "Invoking: `get_current_time` with `{}`\n",
      "\n",
      "\n",
      "\u001b[0m\u001b[33;1m\u001b[1;3m当前时间：2024-08-01 22:19:30。\u001b[0m\u001b[32;1m\u001b[1;3m{'name': 'Response', 'args': {'current_time': '2024-08-01 22:19:30', 'weather': {'杭州市': '雨天', '北京市': '雨天'}}, 'id': 'call_8201119de6cb4077b0a785call_8201119de6cb4077b0a785call_8201119de6cb4077b0a785call_8201119de6cb4077b0a785call_8201119de6cb4077b0a785call_8201119de6cb4077b0a785call_8201119de6cb4077b0a785call_8201119de6cb4077b0a785call_8201119de6cb4077b0a785call_8201119de6cb4077b0a785call_8201119de6cb4077b0a785call_8201119de6cb4077b0a785call_8201119de6cb4077b0a785'}\u001b[0m\n",
      "\n",
      "\u001b[1m> Finished chain.\u001b[0m\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "{'current_time': '2024-08-01 22:19:30', 'weather': {'杭州市': '雨天', '北京市': '雨天'}}"
      ]
     },
     "execution_count": 10,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "agent_executor = AgentExecutor(tools=tools, agent=agent, verbose=True)\n",
    "\n",
    "agent_executor.invoke(\n",
    "    {\"input\": \"杭州和北京天气怎么样？现在几点了？\"},\n",
    "    return_only_outputs=True,\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "langchain",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.2"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
